/*
    model模块：主要负责题库数据的管理，获取题库中相关数据
    <数据库版本的题库: 库名oj_serverdb>
    a、题目列表（编号、标题、难易程度、cpu限制、memery限制）存储在oj_questionslist表
    b、题目的描述、预设代码、测试用例存储在oj_questionsinfo表
    b、题目信息获取后的处理和文件版本相同，只是存储方式和形式不同
*/
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <utility>
#include <fstream>
#include <cassert>
#include "./include/mysql.h"
#include "../util/log.hpp"
#include "../util/util.hpp"

using namespace ns_util;

namespace ns_model
{
    typedef struct Item // 题目列表项
    {
        std::string number; // 题目编号
        std::string title;  // 题目标题
        std::string star;   // 题目难易程度
        int cpulimit;       // cpu资源
        int memlimit;       // 内存资源
    } Item;

    typedef struct Question // 题目所有信息
    {
        Item item;
        std::string desc; // 题目描述
        std::string head; // 预设代码
        std::string tail; // 测试代码
    } Qusetion;

    const std::string host = "139.9.216.193";                // 远端服务器ip
    unsigned int port = 3306;                                // 服务器mysqld端口号
    const std::string user = "oj_server";                    // 登录用户
    const std::string password = "123456789";                // 登陆密码
    const std::string oj_serverdb = "oj_serverdb";           // 库名
    const std::string oj_questionslist = "oj_questionslist"; // 题目列表表名
    const std::string oj_questionsinfo = "oj_questionsinfo"; // 题目描述表表名

    class Model
    {
    private:
        // 获取题目列表
        bool queryQuestionsList(std::string &sql, std::vector<Item> &tokens)
        {
            // 1、初始化句柄
            MYSQL *mysql = mysql_init(nullptr);
            if (mysql == nullptr)
            {
                LOG(FATAL) << "mysql句柄初始化失败\n";
                
                return false;
            }

            // 2、连接数据库
            if (nullptr == mysql_real_connect(mysql, host.c_str(), user.c_str(), password.c_str(), oj_serverdb.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "数据库连接失败\n";
                LOG(FATAL) << mysql_error(mysql);
                return false;
            }

            // 设置字符集：设置连接后编码规则是utf8，可以支持中文(默认是latin1)
            mysql_set_character_set(mysql, "utf8");

            // 3、获取数据
            if (0 != mysql_query(mysql, sql.c_str()))
            {
                LOG(ERROR) << "sql操作失败\n";
                std::cout << sql << std::endl;
                LOG(FATAL) << mysql_error(mysql) << std::endl;
                return false;
            }

            // 4、将句柄中的数据转储到结果集中
            MYSQL_RES *res = mysql_store_result(mysql);

            // 5、将结果集中的数据保存到tokens中
            my_ulonglong rows = mysql_num_rows(res); // 行数量
            Item item;
            for (int i = 0; i < rows; i++)
            {
                MYSQL_ROW it = mysql_fetch_row(res); // MYSQL_ROW : char**
                item.number = it[0];
                item.title = it[1];
                item.star = it[2];
                item.cpulimit = std::stoi(it[3]);
                item.memlimit = std::stoi(it[4]);
                tokens.emplace_back(item);
            }

            // 6、清理工作
            mysql_free_result(res);
            mysql_close(mysql);
            return true;
        }

        bool queryQuestion(const std::string &sql, Question &ques)
        {
            // 1、初始化句柄
            MYSQL *mysql = mysql_init(nullptr);
            if (mysql == nullptr)
            {
                LOG(FATAL) << "mysql句柄初始化失败\n";
                return false;
            }

            // 2、连接数据库
            if (nullptr == mysql_real_connect(mysql, host.c_str(), user.c_str(), password.c_str(), oj_serverdb.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "数据库连接失败\n";
                LOG(FATAL) << mysql_error(mysql) << std::endl;

                return false;
            }

            // 设置字符集：设置连接后编码规则是utf8，可以支持中文(默认是latin1)
            mysql_set_character_set(mysql, "utf8");

            // 3、获取数据
            if (0 != mysql_query(mysql, sql.c_str()))
            {
                LOG(ERROR) << "数据操作失败\n";
                std::cout << sql << std::endl;

                return false;
            }

            // 4、将句柄中的数据转储到结果集中
            MYSQL_RES *res = mysql_store_result(mysql);

            // 5、将结果集中的数据保存到tokens中
            my_ulonglong rows = mysql_num_rows(res); // 行数量(正确情况下只能查到一行)
            for (int i = 0; i < rows; i++)
            {
                MYSQL_ROW it = mysql_fetch_row(res); // MYSQL_ROW : char**
                ques.item.number = it[0];
                ques.item.title = it[1];
                ques.item.star = it[2];
                ques.item.cpulimit = std::stoi(it[3]);
                ques.item.memlimit = std::stoi(it[4]);
                ques.desc = it[5];
                ques.head = it[6];
                ques.tail = it[7];
            }

            // 6、清理工作
            mysql_free_result(res);
            mysql_close(mysql);
            return true;
        }

    public:
        // 获取题目列表
        bool getQuestionsList(std::vector<Item> &tokens)
        {
            std::string sql = "select * from ";
            sql += oj_questionslist;
            if (queryQuestionsList(sql, tokens))
                return true;
            else
                return false;
        }

        // 获取具体题目
        bool getQuestion(const std::string &number, Question &ques)
        {
            std::string sql = "select " + oj_questionslist + ".number, title, star, cpulimit, memlimit, des, head, tail from ";
            sql += oj_questionslist + " inner join " + oj_questionsinfo + " on " + oj_questionslist + ".number=" + oj_questionsinfo + ".number and " + oj_questionsinfo + ".number=" + number;
            if (queryQuestion(sql, ques))
                return true;
            else
                return false;
        }
    };
}